// ohos platform
#ifdef OHOS_KEELS_PLATFORM
#include "pipeline_start.h"
#include "pipeline_start_api.h"

#include <napi/native_api.h>
#include <hilog/log.h>
#include <thread>
#include <uv.h>
#include <ace/xcomponent/native_interface_xcomponent.h>
#include <hilog/log.h>
#include <napi/native_api.h>
#include <thread>

#include "app_main.h"
#include "base/thread/background_task_executor.h"
#include "foundation/arkui/ace_engine/adapter/ohos_keels/stage/ability/stage_asset_provider.h"
#include "foundation/arkui/ace_engine/frameworks/base/utils/system_properties.h"

extern std::thread::id g_mainThreadId;
extern uv_loop_s* g_loop;

namespace Keels {
namespace Rosen {
void CreateOhosWindow(void* window, uint64_t width, uint64_t height);
void StartRSRenderThread();
void DestroyOhosWindow();
void ResetRsRenderThreadWaitingFlag();
void WaitForRSRenderThreadInit();
void StopRSRenderThread();
bool ProcessOhosPointerEvent(OH_NativeXComponent* component, const OH_NativeXComponent_TouchEvent& touchEvent);
} // namespace Rosen
namespace Ace {
namespace NG {
class FrameNode;
void OhosCreateFrameNodeTree();
void OhosResetNodeId();
} // namespace NG
} // namespace Ace
} // namespace Keels

class KeelsGlue {
public:
    KeelsGlue(napi_env env, napi_value glue) : env_(env), glue_(glue), displayDensity_(0) {}
    double GetDisplayDensity()
    {
        if (displayDensity_ != 0) {
            return displayDensity_;
        }
        auto val = GetProperty("displayDensity");
        if (val == nullptr) {
            return defaultValue_;
        }
        napi_valuetype valType;
        auto ret = napi_typeof(env_, val, &valType);
        if (ret != napi_ok) {
            HILOG_DEBUG("failed to get displayDensity's value type");
            return defaultValue_;
        }
        if (valType != napi_number) {
            HILOG_DEBUG("displayDensity should be type of number");
            return defaultValue_;
        }
        double dbVal;
        ret = napi_get_value_double(env_, val, &dbVal);
        if (ret != napi_ok) {
            HILOG_DEBUG("failed to get displayDensity's value");
            return defaultValue_;
        }
        displayDensity_ = dbVal;
        return displayDensity_;
    }

private:
    napi_value GetProperty(const char* name)
    {
        napi_value val;
        auto ret = napi_get_named_property(env_, glue_, name, &val);
        if (ret != napi_ok) {
            HILOG_DEBUG("failed to get property from KeelsGlue, name = %{public}s errocde=%{public}d", name, ret);
            return nullptr;
        }
        return val;
    }

private:
    napi_env env_;
    napi_value glue_;
    double displayDensity_;
    static constexpr double defaultValue_ = 3.25;
};

void XComponentContainer::OnInit(OH_NativeXComponent* x)
{
    xComponentList_.push_back(XComponentContainer(x));
}

bool XComponentContainer::OnPrePipelineStarted(
    OH_NativeXComponent* x, void* w, uint64_t width, uint64_t height, const std::function<void()>& fun)
{
    for (auto it = xComponentList_.begin(); it != xComponentList_.end(); it++) {
        if (it->nativeXComponent_ == x) {
            it->window_ = w;
            it->width_ = width;
            it->height_ = height;
            it->createNodes_ = fun;
        }
    }
    if (xComponentList_.size() == 1) {
        xComponentList_.back().created_ = true;
        return true;
    } else {
        return false;
    }
}

void XComponentContainer::OnSurfaceDestroyed(OH_NativeXComponent* x, void* w)
{
    auto it = xComponentList_.begin();
    while (it != xComponentList_.end()) {
        if (it->nativeXComponent_ == x && it->window_ == w && it->created_) {
            HILOG_DEBUG("XComponentContainer::OnSurfaceDestroyed erase component=%{public}ld window=%{public}ld",
                reinterpret_cast<int64_t>(it->nativeXComponent_), reinterpret_cast<int64_t>(it->window_));
            it = xComponentList_.erase(it);
        } else {
            it++;
        }
    }
    if (!xComponentList_.empty()) {
        xComponentList_.back().created_ = true;
        Keels::Rosen::CreateOhosWindow(
            xComponentList_.back().window_, xComponentList_.back().width_, xComponentList_.back().height_);
        Keels::Rosen::ResetRsRenderThreadWaitingFlag();
        Keels::AbilityRuntime::Platform::AppMain::GetInstance()->DispatchOnCreate("keels", "");
        Keels::Rosen::WaitForRSRenderThreadInit();
        xComponentList_.back().createNodes_();
        HILOG_DEBUG(
            "XComponentContainer::OnSurfaceDestroyed create frame node component=%{public}ld window=%{public}ld",
            reinterpret_cast<int64_t>(xComponentList_.back().nativeXComponent_),
            reinterpret_cast<int64_t>(xComponentList_.back().window_));
    }
}

std::list<XComponentContainer> XComponentContainer::xComponentList_ = {};

OH_NativeXComponent* XComponentContainer::GetLatestNativeXComponent()
{
    if (xComponentList_.empty()) {
        return nullptr;
    }
    return xComponentList_.back().nativeXComponent_;
}

static napi_value ohosOnCreate(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value glue = nullptr;

    napi_get_cb_info(env, info, &argc, &glue, nullptr, nullptr);
    if (argc != 1) {
        HILOG_DEBUG("napi_get_cb_info failed");
        return 0;
    }
    if (glue == nullptr) {
        HILOG_DEBUG("failed to get argument glue");
        return 0;
    }
    napi_valuetype glueType;
    auto ret = napi_typeof(env, glue, &glueType);
    if (ret != napi_ok) {
        HILOG_DEBUG("failed to get glue type, error code=%{public}d", ret);
        return 0;
    }
    if (glueType != napi_object) {
        HILOG_DEBUG("glue type should be object");
        return 0;
    }
    KeelsGlue keels(env, glue);
    Keels::Ace::SystemProperties::SetResolution(keels.GetDisplayDensity());
    HILOG_DEBUG("displayDensity=%{public}f", Keels::Ace::SystemProperties::GetResolution());

    napi_get_uv_event_loop(env, &g_loop);
    HILOG_DEBUG("g_loop %{public}p", g_loop);
    return 0;
}

static void ohosOnSurfaceCreatedx(OH_NativeXComponent* component, void* window)
{
    g_mainThreadId = std::this_thread::get_id();
    uint64_t width, height;
    OH_NativeXComponent_GetXComponentSize(component, window, &width, &height);
    HILOG_DEBUG(
        "ohosOnSurfaceCreatedx, width=%{public}ld, height=%{public}ld, windows=%{public}ld, threadId=%{public}ld",
        width, height, reinterpret_cast<int64_t>(window), std::this_thread::get_id());
    if (XComponentContainer::OnPrePipelineStarted(
            component, window, width, height, []() { Keels::Ace::NG::OhosCreateFrameNodeTree(); }) == false) {
        HILOG_DEBUG("XComponentContainer::OnPrePipelineStarted return false");
        return;
    }
    Keels::AbilityRuntime::Platform::AppMain::GetInstance()->DispatchOnCreate("keels", "");
    Keels::Rosen::CreateOhosWindow(window, width, height);
    Keels::AbilityRuntime::Platform::AppMain::GetInstance()->DispatchOnForeground("keels");
    Keels::Rosen::ResetRsRenderThreadWaitingFlag();
    Keels::Rosen::WaitForRSRenderThreadInit();
    Keels::Ace::NG::OhosCreateFrameNodeTree();
}

static void ohosOnSurfaceChanged(OH_NativeXComponent* component, void* window)
{
    HILOG_DEBUG("ohosOnSurfaceChanged");
}

static void ohosOnSurfaceDestroyed(OH_NativeXComponent* component, void* window)
{
    HILOG_DEBUG("ohosOnSurfaceDestroyed threadId=%{public}ld", std::this_thread::get_id());
    Keels::Rosen::StopRSRenderThread();
    Keels::Rosen::DestroyOhosWindow();
    Keels::Ace::NG::OhosResetNodeId();
    XComponentContainer::OnSurfaceDestroyed(component, window);
}

// TODO: ohos specific code should not be in COMMON code
// Create a ohos WindowView Class to hold the ohos code
// Wrapper WindowView object pointer into XComponent by napi_wrap and napi_define_properties
// In event callback WindowView object can be retrieved
static void ohosDispatchTouchEvent(OH_NativeXComponent* component, void* window)
{
    HILOG_DEBUG("ohosDispatchTouchEvent");

    OH_NativeXComponent_TouchEvent touchEvent {};
    int32_t ret = OH_NativeXComponent_GetTouchEvent(component, window, &touchEvent);
    if (ret != 0) {
        HILOG_DEBUG("OH_NativeXComponent_GetTouchEvent failed ret=%{public}d", ret);
        return;
    }

    if (!Keels::Rosen::ProcessOhosPointerEvent(component, touchEvent)) {
        HILOG_DEBUG("ProcessPointerEvent failed");
    }
}

OH_NativeXComponent_Callback ohos_cb;
OH_NativeXComponent_Callback ohosx_cb;
extern "C" {
static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        { "onCreate", nullptr, ohosOnCreate, nullptr, nullptr, nullptr, napi_default, nullptr },
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);

    napi_value exportInstance = nullptr;
    OH_NativeXComponent* xComponent = nullptr;

    auto status = napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance);
    if (status != napi_ok) {
        return exports;
    }
    status = napi_unwrap(env, exportInstance, reinterpret_cast<void**>(&xComponent));
    if (status != napi_ok || xComponent == nullptr) {
        return exports;
    }
    XComponentContainer::OnInit(xComponent);
    ohosx_cb.OnSurfaceCreated = ohosOnSurfaceCreatedx;
    ohosx_cb.OnSurfaceChanged = ohosOnSurfaceChanged;
    ohosx_cb.OnSurfaceDestroyed = ohosOnSurfaceDestroyed;
    ohosx_cb.DispatchTouchEvent = ohosDispatchTouchEvent;

    auto ret = OH_NativeXComponent_RegisterCallback(xComponent, &ohosx_cb);
    HILOG_DEBUG("OH_NativeXComponent_RegisterCallback error code = %{public}d", ret);

    return exports;
}
}

static napi_module ohosModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "frame",
    .nm_priv = ((void*)0),
    .reserved = { 0 },
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
{
    napi_module_register(&ohosModule);
}

namespace Keels::Ace::Platform {
void KeelsPipelineInitializer::SetCacheDir(const std::string& cacheDir)
{
    AbilityRuntime::Platform::StageAssetProvider::GetInstance()->SetCacheDir(cacheDir);
}

void KeelsPipelineInitializer::SetDisplayDensity(double displayDensity)
{
    Keels::Ace::SystemProperties::SetResolution(displayDensity);
}

void KeelsPipelineInitializer::StartRSRenderThread()
{
    Keels::Rosen::ResetRsRenderThreadWaitingFlag();
    Keels::Rosen::StartRSRenderThread();
}

void KeelsPipelineInitializer::StopRSRenderThread()
{
    Keels::Rosen::StopRSRenderThread();
}

void KeelsPipelineInitializer::SetUVLoop(uv_loop_t* loop)
{
    LOGD("[KeelsPipelineInitializer::SetUVLoop][uv_loop:%{public}p]", loop);
    g_loop = loop;
}
} // namespace Keels::Ace::Platform

#endif
